#include <rid.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include <fn85.h>
#include <rid_fn85.h>

#include <unfy/unfy.h>
#include <unfy/unfp.h>

#define STYLE_9
#include "rls.h"
#include "rlsp.h"

#include "rlsr.h"

#define pass(str) (sizeof(str) - 1)

Rlsr_cmd
rlsr_cmd_parse(const char *str, size_t *pos, const char **error)
{
	switch (*str) {
	case 'a':
		if (strncmp(str, "assert", pass("assert")))
			break;

		*pos += pass("assert");
		return RLSR_ASSERT;
	case 's':
		if (strncmp(str, "search", pass("search")))
			break;

		*pos += pass("search");
		return RLSR_SEARCH;
	case 'v':
		if (strncmp(str, "view", pass("view")))
			break;

		*pos += pass("view");
		return RLSR_VIEW;
	}

	*error = "Invalid command";
	return RLSR_ERROR;
}

int
rlsr_stmnt_parse(Rlsr_stmnt *stmnt, const char *str, size_t *pos,
		 const char **error, Rls_recycle *rec)
{
	size_t tmp_pos = 0;
	size_t id_pos = 0;
	char *num_end;

	stmnt->cmd = rlsr_cmd_parse(str, &tmp_pos, error);

	if (stmnt->cmd == RLSR_ERROR) {
		*pos += tmp_pos;
		return -1;
	}

	if (unfp_space(str + tmp_pos, &tmp_pos, error) < 0) {
		*pos += tmp_pos;
		return -1;
	}

	switch (stmnt->cmd) {
	case RLSR_ASSERT:
		stmnt->id_pos = *pos + tmp_pos;

		switch (rid_fn85_parse(stmnt->id, str + tmp_pos,
				       &id_pos, error)) {
		case FN85_OKAY:
			tmp_pos += id_pos;

			if (unfp_space(str + tmp_pos,
				       &tmp_pos, error) < 0) {
				*pos += tmp_pos;
				return -1;
			}

			break;
		case FN85_TOO_HIGH:
			*pos += tmp_pos + id_pos;
			return -1;
		case FN85_INVALID:
			rid_set(stmnt->id, NULL);
			stmnt->id_pos = *pos + tmp_pos;
			break;
		}

		stmnt->term_pos = *pos + tmp_pos;

		if (!(stmnt->term = unfp_term_parse(str + tmp_pos, &tmp_pos,
						   error, &rec->unfy))) {
			*pos += tmp_pos;
			return -1;
		}

		if (unfp_space(str + tmp_pos, &tmp_pos, error) < 0) {
			*pos += tmp_pos;
			unfy_recycle_term(&rec->unfy, stmnt->term);
			return -1;
		}

		stmnt->list_pos = *pos + tmp_pos;

		if (unfp_list_parse(&stmnt->list, str + tmp_pos, &tmp_pos,
				    error, &rec->unfy) < 0) {
			unfy_recycle_term(&rec->unfy, stmnt->term);
			*pos += tmp_pos;
			return -1;
		}

		*pos += tmp_pos;
		return 0;
	case RLSR_SEARCH:
		stmnt->list_pos = *pos + tmp_pos;
		stmnt->limit = strtol(str + tmp_pos, &num_end, 0);

		if (isspace(*num_end) && str + tmp_pos != num_end) {
			tmp_pos += 1 + (num_end - (str + tmp_pos));
		} else {
			stmnt->limit = -1;
		}

		stmnt->term_pos = *pos + tmp_pos;

		if (!(stmnt->term = unfp_term_parse(str + tmp_pos, &tmp_pos,
						   error, &rec->unfy))) {
			*pos += tmp_pos;
			return -1;
		}

		*pos += tmp_pos;
		return 0;
	case RLSR_VIEW:
		*pos += tmp_pos;
		return 0;
	case RLSR_ERROR:
		break;
	}

	*error = "Unknown error";
	return -1;
}

Unfy_stat
rlsr_searching(Rlsr_lsnr *lsnr, Rlsr_stmnt *stmnt, Rls_rule **rules,
	       Rls_srch **srch, char **str, size_t *size, Rls_info *info,
	       void *extra, Rls_recycle *rec)
{
	Unfy_stat stat;

	while (1) {
		stat = lsnr->search(lsnr, stmnt, srch, rules,
				   info, extra, rec);

		switch (stat) {
		case UNFY_YES:
			printf("YES\n");
			unfp_binds_print((*srch)->fbind, 1);
			printf("SRCH\n");
			rlsp_srch_print(*srch, 1);
			printf("RUN\n");

			if (rls_srch_run(*srch, extra) == UNFY_ERR)
			        goto err;

			printf("\n");
			printf("Continue? (y=yes/other=no) ");

			if (getline(str, size, stdin) < 0 ||
			    strcmp(*str, "y\n")) {
				printf("Ending search.\n");
			        return UNFY_YES;
			}

			continue;
		case UNFY_NO:
			printf("NO\n\t");

			switch (info->rsn) {
			case RLS_RSN_OKAY:
				printf("How did this happend!?\n");
				break;
			case RLS_RSN_END:
				printf("Ran out of rules\n");
				break;
			case RLS_RSN_LIMIT:
			        break;
			case RLS_RSN_UNFY:
				printf("Failed matching\n");
				break;
			}

			if (stmnt->limit || !*srch)
			        return UNFY_NO;

			printf("Matched limit, add amount: ");

			if (getline(str, size, stdin) < 0) {
				printf("Ending search.\n");
			        return UNFY_NO;
			}

			stmnt->limit = strtol(*str, NULL, 0);
			rls_info_dispose(info, rec);
			continue;
		case UNFY_ERR:
			goto err;
		}
	}

err:
	rls_recycle_srch(rec, *srch);
	*srch = NULL;
	return UNFY_ERR;
}

Unfy_stat
rlsr_asserting(Rlsr_lsnr *lsnr, Rlsr_stmnt *stmnt, Rls_rule **rules,
	       void *extra, Rls_recycle *rec)
{
	if (lsnr->assert(lsnr, stmnt, rules, extra, rec) < 0) {
		unfy_recycle_list(&rec->unfy, stmnt->list);
		unfy_recycle_term(&rec->unfy, stmnt->term);
		return UNFY_ERR;
	}

	return UNFY_YES;
}

Unfy_stat
rlsr_default_lsn(Rlsr_lsnr *lsnr, Rls_rule **rules, Rls_srch **srch,
		 char **str, size_t *size, Rls_info *info, void *extra,
		 Rls_recycle *rec)
{
	Rlsr_stmnt stmnt = { 0 };

	if (rlsr_stmnt_parse(&stmnt, *str, &lsnr->pos,
			     &lsnr->error_str, rec) < 0)
		return UNFY_ERR;

	switch (stmnt.cmd) {
	case RLSR_ASSERT:
		return rlsr_asserting(lsnr, &stmnt, rules, extra, rec);
	case RLSR_SEARCH:
		return rlsr_searching(lsnr, &stmnt, rules, srch,
				      str, size, info, extra, rec);
	case RLSR_VIEW:
		rlsp_rule_print(*rules, NULL);
		return UNFY_YES;
	case RLSR_ERROR:
		lsnr->error_str = "Impossible error!";
		return UNFY_ERR;
	}

	return UNFY_YES;
}

int
rlsr_default_assert(Rlsr_lsnr *lsnr, Rlsr_stmnt *stmnt,
		    Rls_rule **rules, void *extra, Rls_recycle *rec)
{
	(void) extra;

	if (rls_rule_add(rules, stmnt->id, stmnt->term,
			 stmnt->list, NULL, rec) < 0) {
		lsnr->error_str = "Failed to assert rule";
		return -1;
	}

	return 0;
}

Unfy_stat
rlsr_default_search(Rlsr_lsnr *lsnr, Rlsr_stmnt *stmnt, Rls_srch **srch_ref,
		    Rls_rule **rules, Rls_info *info, void *extra,
		    Rls_recycle *rec)
{
	Unfy_stat stat = rls_srch_limit(srch_ref, stmnt->term, *rules,
					&stmnt->limit, lsnr->bend, info,
					extra, rec);

	if (stat == UNFY_ERR)
		lsnr->error_str = "Failed searching";

	return stat;
}


void
rlsr_default_error(Rlsr_lsnr *lsnr, void *extra)
{
	(void) extra;
	fprintf(stderr, "%s at %zu\n", lsnr->error_str, lsnr->pos);
}

void
rlsr_lsnr_init(Rlsr_lsnr *lsnr, Rlsr_lsn lsn, Rlsr_assert assert,
	       Rlsr_search search, Rlsr_error error, Rls_bend bend,
	       const char *error_str, size_t pos)
{
	lsnr->lsn = lsn ? lsn : rlsr_default_lsn;
	lsnr->assert = assert ? assert : rlsr_default_assert;
	lsnr->search = search ? search : rlsr_default_search;
	lsnr->error = error ? error : rlsr_default_error;
        lsnr->bend = bend;
	lsnr->error_str = error_str;
	lsnr->pos = pos;
}

void
rlsr_repl(Rls_rule **rules, Rlsr_lsnr *lsnr, void *extra, Rls_recycle *rec)
{
	char *str = NULL;
	size_t size = 0;

	while (1) {
		Rls_info info;
		Rls_srch *srch = NULL;
		Unfy_stat stat;

		lsnr->pos = 0;
		printf("* ");

		if (getline(&str, &size, stdin) < 0) {
			printf("Closing repl\n");
			break;
		}

		stat = lsnr->lsn(lsnr, rules, &srch, &str,
				&size, &info, extra, rec);

		rls_recycle_srch(rec, srch);

		switch (stat) {
		case UNFY_NO:
			rls_info_dispose(&info, rec);
		        continue;
		case UNFY_ERR:
			lsnr->error(lsnr, extra);
			/* fallthru */
		case UNFY_YES:
			continue;
		}
	}

	free(str);
}
